// 熟悉C++11相关互斥锁的接口
// 通过互斥锁维护多线程安全
// 模拟一个黄牛抢票的过程

// 为了确保锁的安全释放
// 1，线程安全设计：
// 使用C++111库中的std::lock_guard实现RAII（资源获取立即初始化）的自动锁管理
// 2，真实模拟要素：
// 随机网络延迟（0-50ms）
// 多个黄牛线程并发竞争
// 确保线程安全和数据一致性

#include <iostream>
#include <random>
#include <thread>
#include <mutex>
#include <chrono> //本质是一个命名空间,C++11新的计时方法
// 相比于传统 <ctime>的计时方法，代码量更高，但精度也更高
// tempalte <intmax_t N,intmax_t D=1> class ratio; N表示分子，D表示分母
// ratio<1,1> second   ratio<60,1> minute ratio<1,1000> microsecond

// 票务管理系统（线程安全）
class TicketSystem
{
public:
    explicit TicketSystem(int total) // explicit关键字修饰构造函数，不能使用隐士转化
        : remain_ticket(total),
          rng(std::random_device{}()) // rand()生成的伪随机数，依赖于种子。
    {} // random_device生成的随机数不依赖于种子，因此可以提供更高质量的随机数

    // 尝试抢票，返回是否成功
    bool grapTicket(int scalper_id)
    {
        std::lock_guard<std::mutex> lock(mtx); // RAII自动加锁解锁
        if (remain_ticket <= 0)
            return false;

        // 模拟网络延迟(0-50ms)
        // 通过伪随机算法，生成随机数0-50
        std::uniform_int_distribution<int> delay(0, 50);
        std::this_thread::sleep_for(std::chrono::milliseconds(delay(rng)));
        // this::thread::sleep_for()是C++11的休眠函数，休眠期间不与其他 线程竞争CPU,等待若干时间

        // 二次检查票数，防止超卖
        if (remain_ticket <= 0)
            return false;

        // 执行抢票操作
        remain_ticket--;
        std::cout << "黄牛[" << scalper_id << "]抢票成功！剩余票数：" << remain_ticket << std::endl;
        return true;
    }
    // 获取剩余票数
    int getTicket() const
    {
        return remain_ticket;
    }

private:
    int remain_ticket; // 剩余票数
    std::mutex mtx;    // 互斥锁
    std::mt19937 rng;  // mt19937是一个伪随机数生成器类，可以生成高质量的伪随机数序列
};

// 黄牛抢票线程函数
void scalperTask(TicketSystem &system, int scalper_id)
{
    while (system.getTicket() > 0)
    {
        if (system.grapTicket(scalper_id))
        {
            // 抢票成功后随机休息
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
    }
}
int main()
{
    const int TOTAL_TICKETS = 100; // 总票数
    const int SCALPER_COUNT = 5;   // 黄牛数量
    TicketSystem ticketsystem(TOTAL_TICKETS);
    std::vector<std::thread> threads;

    // 创建黄牛线程
    for (int i = 0; i < SCALPER_COUNT; i++)
    {
        // 第一个参数为新线程执行函数
        // 后面两个参数为该函数的两个参数
        // C++11引入ref(),用于取某个变量的引用
        threads.emplace_back(scalperTask, std::ref(ticketsystem), i + 1);
    }
    //等待线程结束
    for(auto& t:threads)
    {
        t.join();
    }
    std::cout<<"\n最终剩余票数: "<<ticketsystem.getTicket()<<std::endl;
    return 0;
}